
#include <vector>
#include <fstream>
#include <ctime>
#include "Projection.h"
#include "Camera.h"
#include "BruteForceRenderer.h"
#include "BVHRenderer.h"
#include "BVHRendererThreaded.h"
#include "Scene.h"
#include "PPMWriter.h"
#include "Material.h"
#include "Profiler.h"
#include "OBJReader.h"

std::shared_ptr<Mesh> loadMesh(const std::string &filename) 
{
	std::cout << "Loading mesh " + filename + "..." << std::endl;
	std::shared_ptr<Mesh> mesh = std::make_shared<Mesh>();
	std::ifstream file(filename, std::ifstream::in);
	std::vector<Ogre::Vector3> vertexPositions, vertexNormals;
	OBJReader::readObjFile(file, vertexPositions, vertexNormals, mesh->indexBuffer);
	std::cout << filename << " has " << vertexPositions.size() << " vertices and " << mesh->indexBuffer.size() / 3 << " triangles." << std::endl;
	if (vertexPositions.size() == vertexNormals.size())
	{
		for (unsigned int i = 0; i < vertexPositions.size(); i++)
			mesh->vertexBuffer.push_back(Vertex(vertexPositions[i], vertexNormals[i]));
		mesh->computeTriangleNormals();
		mesh->computeVertexNormals();
	}
	else
	{
		for (unsigned int i = 0; i < vertexPositions.size(); i++)
			mesh->vertexBuffer.push_back(Vertex(vertexPositions[i], Ogre::Vector3(0,0,1)));
		mesh->computeTriangleNormals();
		mesh->computeVertexNormals();
	}
	return mesh;
}

void renderAndOutputFrame(SceneRenderer &renderer, Scene &scene, const Camera &camera, const std::string &imageFileName)
{
	std::vector<Ogre::ColourValue> renderedImage;
	Profiler::Timestamp timeStamp = Profiler::timestamp();
	renderer.renderScene(renderedImage, scene, camera);
	Profiler::printJobDuration("Render image", timeStamp);

	std::ofstream imgFile(imageFileName + ".ppm", std::ios::binary);
	PPMWriter::writePPM(imgFile, renderedImage, camera.getProjection().getWidth(), camera.getProjection().getHeight());
}

void createBunnys(Scene &scene, Camera &camera)
{
	Material blueMaterial(Ogre::ColourValue::Blue, 1.0f, Materials::phong, 0.6f);
	Material redMaterial(Ogre::ColourValue::Red, 1.0f, Materials::phong, 0.6f);
	std::shared_ptr<Mesh> bunny = loadMesh("bunny.obj");
	TransformedMesh *bunny1 = new TransformedMesh(bunny);
	bunny1->setPosition(Ogre::Vector3(-25, -20, 75));
	bunny1->setScale(Ogre::Vector3(100,100,100));
	bunny1->setRotation(Ogre::Quaternion(Ogre::Radian(-Ogre::Math::PI*0.5f), Ogre::Vector3(0,1,0)));
	bunny1->material = blueMaterial;
	scene.meshes.push_back(bunny1);

	TransformedMesh *bunny2 = new TransformedMesh(bunny);
	bunny2->setPosition(Ogre::Vector3(-40, -20, 75));
	bunny2->setScale(Ogre::Vector3(100,100,100));
	bunny2->setRotation(Ogre::Quaternion(Ogre::Radian(-Ogre::Math::PI*0.7f), Ogre::Vector3(0,1,0)));
	bunny2->material = redMaterial;
	scene.meshes.push_back(bunny2);

	TransformedMesh *bunny3 = new TransformedMesh(bunny);
	bunny3->setPosition(Ogre::Vector3(-55, -20, 75));
	bunny3->setScale(Ogre::Vector3(100,100,100));
	bunny3->setRotation(Ogre::Quaternion(Ogre::Radian(-Ogre::Math::PI*0.9f), Ogre::Vector3(0,1,0)));
	bunny3->material = blueMaterial;
	scene.meshes.push_back(bunny3);

	Material chessMaterial(Ogre::ColourValue::White, 1.0f, Materials::chess);
	std::shared_ptr<Mesh> plane = loadMesh("Plane.obj");
	TransformedMesh *movedPlane = new TransformedMesh(plane);
	movedPlane->setPosition(Ogre::Vector3(0, -17.8f, 50.0f));
	movedPlane->setScale(Ogre::Vector3(150,1,150));
	movedPlane->material = chessMaterial;
	scene.meshes.push_back(movedPlane);

	camera.setPositionAndDirection(Ogre::Vector3(30,0,0), Ogre::Vector3(-1, 0, 1).normalisedCopy());

	scene.lights.push_back(new Light(Ogre::Vector3(0, 50, 20), Ogre::ColourValue(1.0f, 1.0f, 1.0f), Ogre::ColourValue::White));
	//scene.lights.push_back(new Light(Ogre::Vector3(-40, 50, 0), Ogre::ColourValue(0.6f, 0.8f, 0.6f), Ogre::ColourValue::White));
}

void createSponzaScene(Scene &scene, Camera &camera)
{
	Material whiteMaterial(Ogre::ColourValue(0.8f, 0.8f, 0.8f), 1.0f, Materials::phong);
	std::shared_ptr<Mesh> sponza = loadMesh("sponza2.obj");
	TransformedMesh *sponza1 = new TransformedMesh(sponza);
	sponza1->setRotation(Ogre::Quaternion(Ogre::Radian(Ogre::Math::PI*0.5f), Ogre::Vector3(0,1,0)));
	sponza1->material = whiteMaterial;
	scene.meshes.push_back(sponza1);

	Ogre::Vector3 camDirection(0, 0, 1);
	camDirection.normalise();
	camera.setPositionAndDirection(Ogre::Vector3(0, 5, -10), camDirection);

	scene.lights.push_back(new Light(Ogre::Vector3(0, 5, 4), Ogre::ColourValue(0.5f, 0.7f, 1.0f), Ogre::ColourValue(0.5f, 0.7f, 1.0f)));
}

void createBuddhaScene(Scene &scene, Camera &camera)
{
	Material goldMaterial(Ogre::ColourValue(0.95f, 0.7f, 0.1f), 1.0f, Materials::phongReflective, 0.7f);
	std::shared_ptr<Mesh> buddha = loadMesh("buddha2.obj");
	TransformedMesh *buddha1 = new TransformedMesh(buddha);
	buddha1->setPosition(Ogre::Vector3(0, 0, 9.0f));
	buddha1->setRotation(Ogre::Quaternion(Ogre::Radian(Ogre::Math::PI*1.2f), Ogre::Vector3(0, 1, 0)));
	buddha1->setScale(Ogre::Vector3(6,6,6));
	buddha1->material = goldMaterial;
	scene.meshes.push_back(buddha1);

	Material chessMaterial(Ogre::ColourValue::White, 1.0f, Materials::chess);
	std::shared_ptr<Mesh> plane = loadMesh("Plane.obj");
	TransformedMesh *movedPlane = new TransformedMesh(plane);
	movedPlane->setPosition(Ogre::Vector3(0, -3.6f, 50.0f));
	movedPlane->setScale(Ogre::Vector3(50,1,50));
	movedPlane->material = chessMaterial;
	scene.meshes.push_back(movedPlane);

	Ogre::Vector3 camDirection(-0.5f, -0.2f, 1);
	camDirection.normalise();
	camera.setPositionAndDirection(Ogre::Vector3(6.0f, 3.0f, -5), camDirection);

	scene.lights.push_back(new Light(Ogre::Vector3(-10, 10, 0), Ogre::ColourValue(0.8f, 0.8f, 0.8f), Ogre::ColourValue(0.8f, 0.8f, 0.8f)));
	scene.lights.push_back(new Light(Ogre::Vector3(-10, 10, 15), Ogre::ColourValue(0.6f, 0.6f, 0.6f), Ogre::ColourValue(0.8f, 0.8f, 0.8f)));

	scene.backgroundColor = Ogre::ColourValue(0.1f, 0.1f, 0.1f);
}

void createBuddhaVideo(SceneRenderer &renderer)
{
	Profiler::Timestamp timeStamp = Profiler::timestamp();
	Camera camera(Ogre::Vector3(0, 0, 0), Ogre::Vector3(0, 0, 1), Projection(1920, 1080, 0.6f));
	Scene scene;
	Material silverMaterial(Ogre::ColourValue(0.2f, 0.2f, 0.2f), 1.0f, Materials::phongReflective, 0.6f);
	Material goldMaterial(Ogre::ColourValue(0.95f, 0.75f, 0.1f), 1.0f, Materials::phongReflective, 0.75f);
	std::shared_ptr<Mesh> buddha = loadMesh("buddha2.obj");
	TransformedMesh *buddha1 = new TransformedMesh(buddha);
	buddha1->setPosition(Ogre::Vector3(0, 0, 9.0f));
	buddha1->setRotation(Ogre::Quaternion(Ogre::Radian(Ogre::Math::PI), Ogre::Vector3(0, 1, 0)));
	buddha1->setScale(Ogre::Vector3(5,5,5));
	buddha1->material = silverMaterial;
	scene.meshes.push_back(buddha1);

	TransformedMesh *buddha2 = new TransformedMesh(buddha);
	buddha2->setPosition(Ogre::Vector3(-2.5, 0, 9.0f));
	buddha2->setRotation(Ogre::Quaternion(Ogre::Radian(Ogre::Math::PI*0.8f), Ogre::Vector3(0, 1, 0)));
	buddha2->setScale(Ogre::Vector3(5,5,5));
	buddha2->material = goldMaterial;
	scene.meshes.push_back(buddha2);

	TransformedMesh *buddha3 = new TransformedMesh(buddha);
	buddha3->setPosition(Ogre::Vector3(2.5, 0, 9.0f));
	buddha3->setRotation(Ogre::Quaternion(Ogre::Radian(Ogre::Math::PI*1.2f), Ogre::Vector3(0, 1, 0)));
	buddha3->setScale(Ogre::Vector3(5,5,5));
	buddha3->material = goldMaterial;
	scene.meshes.push_back(buddha3);

	Ogre::Vector3 camDirection(0, 0, 1);
	camDirection.normalise();
	camera.setPositionAndDirection(Ogre::Vector3(0.0f, 0.2f, 0), camDirection);

	Light *light1 = new Light(Ogre::Vector3(10, 10, 0), Ogre::ColourValue(0.8f, 0.8f, 0.8f), Ogre::ColourValue(0.8f, 0.8f, 0.8f));
	Light *light2 = new Light(Ogre::Vector3(-10, 5, 5), Ogre::ColourValue(0.8f, 0.8f, 0.8f), Ogre::ColourValue(0.8f, 0.8f, 0.8f));
	scene.lights.push_back(light1);
	scene.lights.push_back(light2);

	scene.backgroundColor = Ogre::ColourValue(0.1f, 0.1f, 0.1f);
	Profiler::printJobDuration("Load scene", timeStamp);

	float rotOffset = 0.0f;
	int numFrames = 250;
	for (int i = 0; i < numFrames; i++)
	{
		std::stringstream ss;
		ss << i;
		std::string strNumFrame = ss.str();
		while (strNumFrame.length() < 4) strNumFrame = "0" + strNumFrame;
		renderAndOutputFrame(renderer, scene, camera, "./Video/" + strNumFrame);
		rotOffset += (2.0f*Ogre::Math::PI) / numFrames;
		buddha1->setRotation(Ogre::Quaternion(Ogre::Radian(Ogre::Math::PI + rotOffset), Ogre::Vector3(0, 1, 0)));
		buddha2->setRotation(Ogre::Quaternion(Ogre::Radian(Ogre::Math::PI*0.8f - rotOffset), Ogre::Vector3(0, 1, 0)));
		buddha3->setRotation(Ogre::Quaternion(Ogre::Radian(Ogre::Math::PI*1.2f - rotOffset), Ogre::Vector3(0, 1, 0)));
		light1->position.x -= 20.0f / numFrames;
		light2->position.x += 20.0f / numFrames;
	}
}

void createSphereScene(Scene &scene, Camera &camera)
{
	Material redMaterial(Ogre::ColourValue::Red, 1.0f, Materials::phongReflective);
	Material chessMaterial(Ogre::ColourValue::White, 1.0f, Materials::chess);
	for (int x = 0; x < 3; x++)
		for (int y = 0; y < 3; y++)
			scene.spheres.push_back(new Sphere(Ogre::Vector3(-40.0f, -10.0f + 10*y, 60.0f + 10*x), 4.0f, redMaterial));

	std::shared_ptr<Mesh> plane = loadMesh("Plane.obj");
	TransformedMesh *movedPlane = new TransformedMesh(plane);
	movedPlane->setPosition(Ogre::Vector3(0, -20, 50.0f));
	movedPlane->setScale(Ogre::Vector3(150,1,150));
	movedPlane->material = chessMaterial;
	scene.meshes.push_back(movedPlane);

	scene.lights.push_back(new Light(Ogre::Vector3(60, 50, 0), Ogre::ColourValue(0.8f, 0.6f, 0.6f), Ogre::ColourValue::White));
	scene.lights.push_back(new Light(Ogre::Vector3(-40, 50, 0), Ogre::ColourValue(0.6f, 0.8f, 0.6f), Ogre::ColourValue::White));
}

int main()
{
	BVHRenderer *renderer = new BVHRendererThreaded();
	renderer->setQuadSampling(false);

	Camera camera(Ogre::Vector3(0, 0, 0), Ogre::Vector3(0, 0, 1), Projection(1024, 1024, 0.6f));
	Scene scene;

	Profiler::Timestamp timeStamp = Profiler::timestamp();
	// createBuddhaScene(scene, camera);
	createSponzaScene(scene, camera);
	//createBunnys(scene, camera);
	Profiler::printJobDuration("Load scene", timeStamp);

	renderAndOutputFrame(*renderer, scene, camera, "render");
	delete renderer;	// make sure that the renderer is destroyed before the scene
	//createBuddhaVideo(*renderer);

	while (true) {}

	return 0;
}